﻿
// -------------------------------------------------------------------------------------------------
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// -------------------------------------------------------------------------------------------------

using QInjection.Injector.Contracts;
using QInjection.Injector.Instructors;
using Roslyn.Compilers.CSharp;
using Roslyn.Scripting.CSharp;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace QInjection.Injector
{
    public class QInjector
    {
        private Configuration _configuration;

        private Phase _phase;

        private String _expression;

        private Node? _currentnode;
        private Type _currentnodetype;
        private Element? _currentelement;

        private NodeToTypeMap _nodetotypeMap;
        private OperatorCategoryMap _operatorcategoryMap;
        private ElementsMap _elementsMap;

        private RulesSet _ruleset;
        private BranchSet _branchset;

        private TreeValidator _validator;

        private Boolean UsingAccept { get { return _currentelement.HasValue; } }

        private INodesInstructor _UnaryInstructor = new UnaryInstructor();
        private INodesInstructor _BinaryInstructor = new BinaryInstructor();

        #region Constructors
        public QInjector(string expression)
        {
            if (String.IsNullOrEmpty(expression))
                throw new Exception("Expression is null or empty");
            _expression = expression;
            this.Initialize();
        }

        public QInjector()
        {
            this.Initialize();
        }

        private void Initialize()
        {
            _configuration = Configuration.Instance;

            _nodetotypeMap = NodeToTypeMap.Instance;
            _operatorcategoryMap = OperatorCategoryMap.Instance;
            _elementsMap = new ElementsMap(_configuration.GetDefaultInstructor());

            _phase = Phase.None;
            _currentnode = null;
            _currentnodetype = null;
            _currentelement = null;

            _ruleset = new RulesSet();
            _branchset = new BranchSet();
            _validator = new TreeValidator();
        }
        #endregion

        #region Getters
        public INodesInstructor BinaryInstructor { get { return _BinaryInstructor; } }
        public INodesInstructor UnaryInstructor { get { return _UnaryInstructor; } }
        #endregion



        #region Utilities
        private String CreateWrapperCode()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("namespace WrapperNameSpace { class WrapperClass { static void WrapperMethod() { ");
            sb.Append(_expression);
            sb.Append("} }");
            return sb.ToString();
        }

        private void SetElement(Element element)
        {
            _currentelement = element;
            _currentnode = null;
            _currentnodetype = null;
        }

        private void SetElement(Element? element)
        {
            _currentelement = element;
            _currentnode = null;
            _currentnodetype = null;
        }

        private void SetNode(Node node)
        {
            _currentelement = null;
            _currentnode = node;
            _currentnodetype = _nodetotypeMap[node];
        }
        #endregion

        #region Validator
        public void Validate()
        {
            if (_phase != Phase.None)
                throw new Exception("Mismatched phase");

            if (String.IsNullOrEmpty(_expression))
                throw new Exception("Expression is Empty or Null");

            this.Validate(_expression);
        }

        public void Validate(string expression)
        {
            if (_phase != Phase.None)
                throw new Exception("Mismatched phase");

            if (String.IsNullOrEmpty(expression))
                throw new Exception("Expression is Empty or Null");

            if (_branchset == null || _branchset.Count() == 0)
                throw new Exception("Branches have not been set");

            if (!_branchset.ContainsKey(Branch.Input))
                throw new Exception("No Input Branch set");

            _expression = expression;

            var wrappercode = CreateWrapperCode();
            SyntaxTree treewrapper = SyntaxTree.ParseText(wrappercode);

            ValidateSyntaxTree(treewrapper);
        }

        private void ValidateSyntaxTree(SyntaxTree tree)
        {

            QueueSyntax queue = new QueueSyntax();

            var root = (CompilationUnitSyntax)tree.GetRoot();

            //
            // Validation State Machine
            //
            //      01 - NamespaceDeclarationSyntax
            //      02 - ClassDeclarationSyntax
            //      03 - MethodDeclarationSyntax
            //      04 - BlockSyntax
            //      05 - ExpressionStatementSyntax
            //      06 - Validate the Lambda Expression
            //      07 - DONE, syntax is correct
            // 
            int vsm = 01;
            Boolean parsedone = false;

            queue.Enqueue(root.Members);

            while (true)
            {
                if (parsedone == true)
                    break;

                var n = queue.Dequeue();

                if (n == null)
                    throw new Exception("No element found");
                switch (vsm)
                {
                    case 1:
                        {
                            if (n is NamespaceDeclarationSyntax)
                                queue.Enqueue((n as NamespaceDeclarationSyntax).Members);
                            else
                                throw new Exception("No NamespaceDeclaration found");
                            vsm = 2;
                        }
                        break;
                    case 2:
                        {
                            if (n is ClassDeclarationSyntax)
                                queue.Enqueue((n as ClassDeclarationSyntax).Members);
                            else
                                throw new Exception("No ClassDeclaration found");
                            vsm = 3;
                        }
                        break;
                    case 3:
                        {
                            if (n is MethodDeclarationSyntax)
                            {
                                var meth = (n as MethodDeclarationSyntax);
                                if (meth.Body == null)
                                    throw new FormatException("No Body found");
                                queue.Enqueue((SyntaxNode)meth.Body);
                                vsm = 4;
                            }
                            else
                                throw new Exception("No MethodDeclaration found");
                        }
                        break;
                    case 4:
                        {
                            if (n is BlockSyntax)
                            {
                                var block = (n as BlockSyntax);
                                if (block.Statements == null)
                                    throw new FormatException("No Statements found");
                                queue.Enqueue(block.Statements);
                                vsm = 5;
                            }
                            else
                                throw new Exception("No Block found");
                        }
                        break;
                    case 5:
                        {
                            if (n is ExpressionStatementSyntax)
                            {
                                var expr = (n as ExpressionStatementSyntax);
                                var realexpr = expr.Expression;
                                queue.Enqueue(realexpr);
                                vsm = 6;
                            }
                            else
                                throw new Exception("No ExpressionStatement found");
                        }
                        break;
                    case 6:
                        {
                            _validator.Run(_branchset, _ruleset, n);
                            parsedone = true;
                        }
                        break;
                    case 7:
                        {
                            // You won
                        }
                        break;
                }
            }



        }
        #endregion

        #region Script
        public IEnumerable<T> WhereOn<T>(IQBaseProvider provider)
        {
            //
            //
            // http://msdn.microsoft.com/en-us/library/twcad0zb.aspx
            // http://social.msdn.microsoft.com/Forums/en-US/b1f911e3-a542-4299-b0ce-01364f5ad2ec/generic-method-return-type-casting-problem?forum=csharplanguage
            //
            //

            //Type objectType = null;
            //Type[] typeArgs = null;

            //var ifs = c.GetType().GetInterfaces();
            //foreach (var i in ifs)
            //    if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISQueryProvider<>))
            //    {
            //        objectType = i.GetGenericArguments()[0];
            //        typeArgs = i.GetGenericArguments();
            //    }

            //Type type = typeof(SQueryResponse<>).MakeGenericType(typeArgs);
            //object myObject = Activator.CreateInstance(type);

            //return (SQueryResponse<T>)((object)(myObject));

            //
            // Get the Type T of the Generic
            // 
            Type objectType = null;
            var ifs = provider.GetType().GetInterfaces();
            foreach (var i in ifs)
                if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQProvider<>))
                {
                    objectType = i.GetGenericArguments()[0];
                }

            if (objectType == null)
                throw new Exception("Generic type not found");

            if (objectType.FullName != typeof(T).FullName)
                throw new Exception("Mismatched type between method and interface");

            // Prepare the Session
            //
            var squeryprovider = (IQProvider<T>)provider;

            //
            // Get the type full name
            //
            var fullname = objectType.FullName;

            // Trick: try to work with IEnumerable
            var ifs2 = objectType.GetInterfaces();
            foreach (var i in ifs2)
                if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                {
                    var t = i.GetGenericArguments()[0];
                }
                else if (i == typeof(IEnumerable))
                {
                    var args = objectType.GetGenericArguments();
                    if (args.Count() > 1)
                        throw new Exception("IEnumerable with more than an generic arguments");
                    fullname = "System.Collections.Generic.IEnumerable<" + args[0].FullName + ">";
                }

            StringBuilder sb = new StringBuilder();
            sb.Append("public class Executor");
            sb.Append("{");
            sb.Append("  public System.Collections.Generic.IEnumerable<" + fullname + "> Where(System.Collections.Generic.IEnumerable<" + fullname + "> list)");
            sb.Append("  {");
            sb.Append("     return list.Where(" + _expression + ").ToList();");
            sb.Append("  }");
            sb.Append("}");

            var CodeText = sb.ToString();

            SyntaxTree tree = SyntaxTree.ParseText(CodeText);
            var root = (CompilationUnitSyntax)tree.GetRoot();

            // Istantiate Session and Engine
            //
            var engine = new ScriptEngine();
            var session = engine.CreateSession(squeryprovider, typeof(IQProvider<T>));

            session.AddReference("System");
            session.AddReference("System.Core");
            session.AddReference("System.Data");
            session.AddReference("System.Data.Linq");
            session.AddReference(squeryprovider.GetType().Assembly.Location);

            session.ImportNamespace(@"System.Collections.Generic");

            session.Execute(@"using System.Collections.Generic;");
            session.Execute(@"using System.Linq;");

            session.Execute(CodeText);
            session.Execute(@"var executor = new Executor();");

            var result = (IEnumerable<T>)session.Execute(@"executor.Where(QueryInjectorGetIEnumerable());");

            return result;

        }
        #endregion

        #region Validation Syntax Definition

        #region HPP
        public QInjector Accept(Element element)
        {
            if (!_elementsMap.ContainsKey(element))
            {
                _currentnode = null;
                _currentnodetype = null;
                _currentelement = null;
                throw new Exception("Element not supported on Accept");
            }

            SetElement(element);

            return this;
        }

        public QInjector AsBranch()
        {
            Element? _backup = _currentelement;

            if (!_currentelement.HasValue)
                throw new Exception("Any element has been set");

            foreach (var m in _elementsMap[_currentelement.Value])
            {
                this.Support(m.Node).As(m.Branch);
            }

            SetElement(_backup);
            
            return this;
        }
        #endregion

        #region LPP
        public QInjector Support(Node node)
        {
            _currentnodetype = _nodetotypeMap[node];
            _currentnode = node;
            _currentelement = null;
            return this;
        }

        public QInjector As(Branch branch)
        {
            if (_currentnodetype == null)
                throw new Exception("Any node has been set");

            if (branch == Branch.Input && _currentnodetype != typeof(SimpleLambdaExpressionSyntax))
                throw new Exception("This version supports only lambda expression as input");

            _branchset.Add(branch, _currentnodetype);

            return this;
        }
        #endregion

        #region Restrictions
        public QInjector WithArity(int arity)
        {
            if (arity < 0)
                throw new Exception("Invalid arity");

            if (!UsingAccept)
            {
                if (!_currentnode.HasValue)
                    throw new Exception("Any node has been set");

                if (_currentnode != Node.InvocationExpression)
                    throw new Exception("Feature not support on this Syntax Node's Type");

                InternalWithArity(_currentnode.Value, arity);

            }
            else
            {
                (from e in _elementsMap[_currentelement.Value] select e.Node)
                .Distinct().ToList()
                .ForEach(x => InternalWithArity(x, arity));
            }
            return this;
        }

        public QInjector WithOperators(OperatorCategory operatorcategory)
        {
            foreach (var item in _operatorcategoryMap[operatorcategory]) WithOperator(item);
            return this;
        }

        public QInjector WithOperators(IEnumerable<Operator> operators)
        {
            foreach (var item in operators) WithOperator(item);
            return this;
        }

        public QInjector WithOperator(Operator _operator)
        {
            if (!UsingAccept)
            {

                if (!_currentnode.HasValue)
                    throw new Exception("Any node has been set");

                if (_currentnode.Value != Node.BinaryExpression &&
                    _currentnode.Value != Node.PostfixUnaryExpression &&
                    _currentnode.Value != Node.PrefixUnaryExpression)
                    throw new Exception("Feature not supported on this Syntax Node's Type");

                InternalWithOperator(_currentnode.Value, _operator);
            }
            else
            {
                (from e in _elementsMap[_currentelement.Value] select e.Node)
                .Distinct().ToList()
                .ForEach(x => InternalWithOperator(x, _operator));
            }
            return this;
        }

        public QInjector WithName(IEnumerable<String> names)
        {
            foreach (var item in names) WithName(item);
            return this;
        }

        public QInjector WithName(String name)
        {
            if (String.IsNullOrEmpty(name))
                throw new Exception("Invalid name");

            if (!UsingAccept)
            {

                if (!_currentnode.HasValue)
                    throw new Exception("Any node has been set");

                if (_currentnode.Value != Node.InvocationExpression &&
                    _currentnode.Value != Node.MemberAccessExpression)
                    throw new Exception("Feature not supported on this Syntax Node's Type");

                InternalWithName(_currentnode.Value, name);
            }
            else
            {
                (from e in _elementsMap[_currentelement.Value] select e.Node)
                .Distinct().ToList()
                .ForEach(x => InternalWithName(x, name));
            }
            return this;
        }

        public QInjector WithNameRegex(IEnumerable<String> regexs)
        {
            foreach (var item in regexs) WithNameRegex(item);
            return this;
        }

        public QInjector WithNameRegex(string regex)
        {
            if (String.IsNullOrEmpty(regex))
                throw new Exception("Invalid regex");

            if (!UsingAccept)
            {

                if (!_currentnode.HasValue)
                    throw new Exception("Any node has been set");

                if (_currentnode.Value != Node.InvocationExpression &&
                    _currentnode.Value != Node.MemberAccessExpression)
                    throw new Exception("Feature not supported on this Syntax Node's Type");

                InternalWithNameRegex(_currentnode.Value, regex);
            }
            else
            {
                (from e in _elementsMap[_currentelement.Value] select e.Node)
                .Distinct().ToList()
                .ForEach(x => InternalWithNameRegex(x, regex));
            }
            return this;
        }


        public QInjector RefuseReference(IEnumerable<Reference> references)
        {
            foreach (var item in references) RefuseReference(item);
            return this;
        }

        public QInjector RefuseReference(Reference reference)
        {
            if (!UsingAccept)
            {

                if (!_currentnode.HasValue)
                    throw new Exception("Any node has been set");

                if (_currentnode.Value != Node.Argument)
                    throw new Exception("Feature not supported on this Syntax Node's Type");

                InternalRefuseReference(_currentnode.Value, reference);
            }
            else
            {
                (from e in _elementsMap[_currentelement.Value] select e.Node)
                .Distinct().ToList()
                .ForEach(x => InternalRefuseReference(x, reference));
            }
            return this;
        }
        #endregion

        #region Internal methods
        private QInjector InternalWithArity(Node node, int arity)
        {
            RuleData rdata = new RuleData(Restriction.Arity, null);
            rdata.Set(arity);

            _ruleset.Add(node, _nodetotypeMap[node], rdata);

            return this;
        }

        public QInjector InternalWithOperator(Node node, Operator _operator)
        {
            RuleData rdata = new RuleData(Restriction.Operator, null);
            rdata.Set(_operator);

            _ruleset.Add(node, _nodetotypeMap[node], rdata);

            return this;
        }

        public QInjector InternalWithName(Node node, string name)
        {
            RuleData rdata = new RuleData(Restriction.Name, null);
            rdata.Set(name);

            _ruleset.Add(node, _nodetotypeMap[node], rdata);

            return this;
        }

        public QInjector InternalWithNameRegex(Node node, string regex)
        {
            Regex rg = new Regex(regex);

            RuleData rdata = new RuleData(Restriction.NameRegex, null);
            rdata.Set(rg);

            _ruleset.Add(node, _nodetotypeMap[node], rdata);

            return this;
        }

        public QInjector InternalRefuseReference(Node node, Reference reference)
        {
            RuleData rdata = new RuleData(Restriction.Reference, null);
            rdata.Set(reference);

            _ruleset.Add(node, _nodetotypeMap[node], rdata);

            return this;
        }
        #endregion

        #region Statements
        public void Begin()
        {
            _phase = Phase.Coding;
            this.Support(Node.SimpleLambdaExpression).As(Branch.Input);
            this.Support(Node.Parameter).As(Branch.Parameter);
        }

        public void End()
        {
            _phase = Phase.None;
        }
        #endregion

        #endregion

        #region Teaching
        public void Teach(IEnumerable<IElementsInstructor> instructors)
        {
            foreach (var i in instructors)
                Teach(i);
        }

        public void Teach(IEnumerable<INodesInstructor> instructors)
        {
            foreach (var i in instructors)
                Teach(i);
        }

        public void Teach(IElementsInstructor instructor)
        {
            IEnumerable<Element> __elements = instructor.Elements ?? new List<Element>();

            this.Begin();

            try
            {
                // Add Elements
                foreach (var e in __elements)
                {
                    this.Accept(e);
                    this.AsBranch();
                    // ... and their restrictions
                    IEnumerable<Tuple<Restriction, object>> __restrictions = instructor.Restrictions(e) ?? new List<Tuple<Restriction, object>>();
                    foreach (var r in __restrictions)
                    {
                        AddRestriction(r);
                    }
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                this.End();
            }
        }

        public void Teach(INodesInstructor instructor)
        {
            IEnumerable<Node> __nodes = instructor.Nodes ?? new List<Node>();

            this.Begin();

            try
            {
                // Add Nodes
                foreach (var n in __nodes)
                {
                    this.Support(n);
                    IEnumerable<Branch> __branches = instructor.Branches(n) ?? new List<Branch>();
                    foreach (var b in instructor.Branches(n))
                    {
                        this.As(b);
                    }
                    // ... and their restrictions
                    IEnumerable<Tuple<Restriction, object>> __restrictions = instructor.Restrictions(n) ?? new List<Tuple<Restriction, object>>();
                    foreach (var r in __restrictions)
                    {
                        AddRestriction(r);
                    }
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                this.End();
            }
        }

        public void Teach(IInstructor instructor)
        {
            IEnumerable<Element> __elements = instructor.Elements ?? new List<Element>();
            IEnumerable<Node> __nodes = instructor.Nodes ?? new List<Node>();

            this.Begin();

            try
            {
                // Add Elements
                foreach (var e in __elements)
                {
                    this.Accept(e);
                    this.AsBranch();
                    // ... and their restrictions
                    IEnumerable<Tuple<Restriction, object>> __restrictions = instructor.Restrictions(e) ?? new List<Tuple<Restriction, object>>();
                    foreach (var r in __restrictions)
                    {
                        AddRestriction(r);
                    }
                }

                // Add Nodes
                foreach (var n in __nodes)
                {
                    this.Support(n);
                    IEnumerable<Branch> __branches = instructor.Branches(n) ?? new List<Branch>();
                    foreach (var b in instructor.Branches(n))
                    {
                        this.As(b);
                    }
                    // ... and their restrictions
                    IEnumerable<Tuple<Restriction, object>> __restrictions = instructor.Restrictions(n) ?? new List<Tuple<Restriction, object>>();
                    foreach (var r in __restrictions)
                    {
                        AddRestriction(r);
                    }
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                this.End();
            }
        }

        private void AddRestriction(Tuple<Restriction, object> restriction)
        {
            switch (restriction.Item1)
            {
                case Restriction.Operator:
                    this.WithOperator((Operator)restriction.Item2);
                    break;
                case Restriction.Name:
                    this.WithName((String)restriction.Item2);
                    break;
                case Restriction.NameRegex:
                    this.WithNameRegex((String)restriction.Item2);
                    break;
                case Restriction.Arity:
                    this.WithArity((int)restriction.Item2);
                    break;
                case Restriction.Reference:
                    this.RefuseReference((Reference)restriction.Item2);
                    break;
            }
        }

        #endregion
    }
}
